﻿#pragma once

#include <utility>
#include <memory>
#include <stdexcept>
#include <functional>
#include <type_traits>

#include "utility.hpp"
#include "type_traits.hpp"
#include "none.hpp"

#include "config/config_begin.h"

namespace cpp{

	namespace __optional{ struct _Nullopt{}; }

	struct nullopt_t { explicit constexpr nullopt_t(__optional::_Nullopt) {} };

	constexpr const nullopt_t nullopt{ __optional::_Nullopt{} };

	class bad_optional_access :public std::logic_error
	{
	public:
		bad_optional_access() :logic_error("bad optional access")	{}
	};

	namespace __optional{

		struct _DummyNullOptional{};

		template<class T>
		using _Destructible = decltype((int(), std::declval<T>().~T()));

		template<class T>
		class _ConstexprOptional
		{
		protected:
			union
			{
				std::remove_cv_t<T> _value;
				_DummyNullOptional _nil;
			};
			bool _has_value;
		public:
			constexpr _ConstexprOptional() noexcept : _nil{}, _has_value(false){}

			constexpr _ConstexprOptional(nullopt_t) noexcept : _nil{}, _has_value(false){}

			constexpr _ConstexprOptional(const T& value)
				: _value(value)
				, _has_value(true)
			{

			}

			_ConstexprOptional(const _ConstexprOptional& other)
				:_has_value(other._has_value)
			{
				if(other._has_value)
				{
					::new (static_cast<void*>(&_value)) T(other._value);
				}
			}

			_ConstexprOptional(_ConstexprOptional&& other) noexcept(std::is_nothrow_move_constructible<T>::value)
				:_has_value(other._has_value)
			{
				if(other._has_value)
				{
					::new (static_cast<void*>(&_value)) T(std::move(other._value));
				}
			}

			constexpr _ConstexprOptional(T&& value)
				:_value(std::move(value))
				, _has_value(true)
			{
			}

			template< class... Args >
			constexpr explicit _ConstexprOptional(in_place_t, Args&& ... args)
				:_value(std::forward<Args>(args)...)
				, _has_value(true)
			{
			}

			template< class U, class... Args, class = std::enable_if_t< std::is_constructible<T, std::initializer_list<U>&, Args&& ...>::value>  >
			constexpr explicit _ConstexprOptional(in_place_t, std::initializer_list<U> ilist, Args&& ... args)
				:_value(ilist, std::forward<Args>(args)...)
				, _has_value(true)
			{

			}

			void operator=(nullopt_t) noexcept
			{
				reset();
			}

			void operator=(const _ConstexprOptional& other)
			{
				if(_has_value)
				{
					if(other._has_value)
					{
						_value = other._value;
					}
					else
					{
						reset();
					}
				}
				else
				{
					if(other._has_value)
					{
						::new (static_cast<void*>(&_value)) T(other._value);
						_has_value = true;
					}
				}
			}

			void operator=(_ConstexprOptional&& other) noexcept(std::is_nothrow_move_assignable<T>::value&&
																std::is_nothrow_move_constructible<T>::value)
			{
				if(_has_value)
				{
					if(other._has_value)
					{
						_value = std::move(other._value);
					}
					else
					{
						reset();
					}
				}
				else
				{
					if(other._has_value)
					{
						::new (static_cast<void*>(&_value)) T(std::move(other._value));
						_has_value = true;
					}
				}
			}

			template< class U, class _Dummy = std::enable_if_t< std::is_constructible<T, U&&>::value&& std::is_assignable<T&, U&&>::value> >
			void operator=(U&& value)
			{
				_value = std::forward<U>(value);
				_has_value = true;
			}

			void reset() noexcept
			{
				_has_value = false;
			}


			template< class... Args >
			void emplace(Args&& ... args)
			{
				::new (static_cast<void*>(&_value)) T(std::forward<Args>(args)...);
				_has_value = true;
			}
			template< class U, class... Args >
			void emplace(std::initializer_list<U> ilist, Args&& ... args)
			{
				::new (static_cast<void*>(&_value)) T(ilist, std::forward<Args>(args)...);
				_has_value = true;
			}


			void swap(_ConstexprOptional& other) noexcept(noexcept (std::swap(*this, other)))
			{
				std::swap(*this, other);
			}
		};

		//
		template<class T>
		class _Optional
		{
		protected:
			union
			{
				std::remove_cv_t<T> _value;
				_DummyNullOptional _nil;
			};
			bool _has_value;
		public:
			constexpr _Optional() noexcept : _nil{}, _has_value(false){}

			constexpr _Optional(nullopt_t)  noexcept : _nil{}, _has_value(false){}

			constexpr _Optional(const T& value)
				: _value(value)
				, _has_value(true)
			{

			}

			_Optional(const _Optional& other)
				:_has_value(other._has_value)
			{
				if(other._has_value)
				{
					::new (static_cast<void*>(&_value)) T(other._value);
				}
			}

			_Optional(_Optional&& other) noexcept(std::is_nothrow_move_constructible<T>::value)
				:_has_value(other._has_value)
			{
				if(other._has_value)
				{
					::new (static_cast<void*>(&_value)) T(std::move(other._value));
				}
			}
			constexpr _Optional(T&& value)
				:_value(std::move(value))
				, _has_value(true)
			{
			}

			template< class... Args >
			constexpr explicit _Optional(in_place_t, Args&& ... args)
				:_value(std::forward<Args>(args)...)
				, _has_value(true)
			{
			}

			template< class U, class... Args, class = std::enable_if_t< std::is_constructible<T, std::initializer_list<U>&, Args&& ...>::value>  >
			constexpr explicit _Optional(in_place_t, std::initializer_list<U> ilist, Args&& ... args)
				:_value(ilist, std::forward<Args>(args)...)
				, _has_value(true)
			{

			}

			void operator=(const _Optional& other)
			{
				if(_has_value)
				{
					if(other._has_value)
					{
						_value = other._value;
					}
					else
					{
						reset();
					}
				}
				else
				{
					if(other._has_value)
					{
						::new (static_cast<void*>(&_value)) T(other._value);
						_has_value = true;
					}
				}

			}

			void operator=(_Optional&& other) noexcept(std::is_nothrow_move_assignable<T>::value&&
													   std::is_nothrow_move_constructible<T>::value)
			{
				if(_has_value)
				{
					if(other._has_value)
					{
						_value = std::move(other._value);
					}
					else
					{
						reset();
					}
				}
				else
				{
					if(other._has_value)
					{
						::new (static_cast<void*>(&_value)) T(std::move(other._value));
						_has_value = true;
					}
				}
			}

			template< class U, class _Dummy = std::enable_if_t< std::is_constructible<T, U&&>::value&& std::is_assignable<T&, U&&>::value> >
			void operator=(U&& value)
			{
				if(_has_value)
				{
					_value = std::forward<U>(value);
				}
				else
				{
					::new (static_cast<void*>(&_value)) T(std::forward<U>(value));
					_has_value = true;
				}
			}

			~_Optional()
			{
				reset();
			}



			void reset() noexcept
			{
				if(_has_value)
				{
					_value.~T();
					_has_value = false;
				}
			}

			template< class... Args >
			void emplace(Args&& ... args)
			{
				reset();
				::new (static_cast<void*>(&_value)) T(std::forward<Args>(args)...);
				_has_value = true;
			}
			template< class U, class... Args >
			void emplace(std::initializer_list<U> ilist, Args&& ... args)
			{
				reset();
				::new (static_cast<void*>(&_value)) T(ilist, std::forward<Args>(args)...);
				_has_value = true;
			}


			void swap(_Optional& other)	noexcept(std::is_nothrow_move_constructible<T>::value &&
												 noexcept(utility::swap_adl(std::declval<T&>(), std::declval<T&>())))
			{

				if(_has_value)
				{
					if(other._has_value)
					{
						using std::swap;
						swap(_value, other._value);
					}
					else
					{
						::new (static_cast<void*>(&other._value)) T(std::move(_value));
						other._has_value = true;
						reset();
					}
				}
				else
				{
					if(other._has_value)
					{
						::new (static_cast<void*>(&_value)) T(std::move(other._value));
						_has_value = true;
						other.reset();
					}
				}
			}
		};

		template<class T>
		using _OptionalBase =
			std::conditional_t< std::is_trivially_destructible<T>::value, _ConstexprOptional<T>, _Optional<T>>;
	}

	template< class T >
	class optional :public __optional::_OptionalBase<T>
	{
		static_assert(std::is_destructible_v<T> && !std::is_array_v<T> && !std::is_reference_v<T>, "The type must meet the requirements of Destructible");

		using base_type = __optional::_OptionalBase<T>;
	public:
		using  value_type = T;

		constexpr optional() noexcept {};

		constexpr optional(nullopt_t) noexcept {};

		optional(const optional& other) = default;
		optional(optional&& other) noexcept(std::is_nothrow_move_constructible<T>::value) = default;

		constexpr optional(const T& value) :base_type(value){}

		constexpr optional(T&& value) : base_type(std::move(value)){}


		template< class... Args >
		constexpr explicit optional(in_place_t, Args&& ... args)
			:base_type(in_place, std::forward<Args>(args)...){}

		template< class U, class... Args, class = std::enable_if_t< std::is_constructible<T, std::initializer_list<U>&, Args&& ...>::value>  >
		constexpr explicit optional(in_place_t, std::initializer_list<U> ilist, Args&& ... args)
			:base_type(in_place, ilist, std::forward<Args>(args)...){}

		~optional() = default;

		optional& operator=(nullopt_t) noexcept
		{
			reset();
			return *this;
		}

		optional& operator=(const optional& other)
		{
			if(this == &other)
			{
				return *this;
			}
			base_type::operator=(other);
			return *this;
		}

		optional& operator=(optional&& other) noexcept(std::is_nothrow_move_assignable<T>::value&&
													   std::is_nothrow_move_constructible<T>::value)
		{
			if(this == &other)
			{
				return *this;
			}
			base_type::operator=(std::move(other));
			return *this;
		}

		template< class U, class _Dummy = std::enable_if_t< std::is_constructible<T, U&&>::value&& std::is_assignable<T&, U&&>::value> >
		optional& operator=(U&& value)
		{
			base_type::operator= (std::forward<U>(value));
			return *this;
		}

		constexpr explicit operator bool() const noexcept
		{
			return base_type::_has_value;
		}

		constexpr bool has_value() const noexcept
		{
			return base_type::_has_value;
		}

		void reset() noexcept
		{
			base_type::reset();
		}

		template< class... Args >
		void emplace(Args&& ... args)
		{
			base_type::emplace(std::forward<Args>(args)...);
		}
		template< class U, class... Args >
		void emplace(std::initializer_list<U> ilist, Args&& ... args)
		{
			base_type::emplace(ilist, std::forward<Args>(args)...);
		}


		constexpr const T& value() const &
		{
			if(!base_type::_has_value)
			{
				throw bad_optional_access();
			}
			return base_type::_value;
		}

		constexpr T& value() &
		{
			if(!base_type::_has_value)
			{
				throw bad_optional_access();
			}
			return base_type::_value;
		}

		constexpr const T&& value() const &&
		{
			if(!base_type::_has_value)
			{
				throw bad_optional_access();
			}
			return std::move(base_type::_value);
		}

		constexpr T&& value() &&
		{
			if(!base_type::_has_value)
			{
				throw bad_optional_access();
			}
			return std::move(base_type::_value);
		}

		template< class U >
		constexpr T value_or(U&& value) const &
		{
			return base_type::_has_value ? base_type::_value : static_cast<T>(std::forward<U>(value));
		}

		template< class U >
		constexpr T value_or(U&& value) &&
		{
			return base_type::_has_value ? std::move(base_type::_value) : static_cast<T>(std::forward<U>(value));
		}

		constexpr const T& operator*() const &
		{
			return base_type::_value;
		}
		constexpr T& operator*() &
		{
			return base_type::_value;
		}

		constexpr const T&& operator*() const &&
		{
			return  std::move(base_type::_value);
		}

		constexpr T&& operator*() &&
		{
			return  std::move(base_type::_value);
		}

		constexpr const T* operator->() const
		{
			return &this->base_type::_value;
		}

		constexpr T* operator->()
		{
			return &this->base_type::_value;
		}

		void swap(optional& other)	noexcept(std::is_nothrow_move_constructible<T>::value &&
											 noexcept(utility::swap_adl(std::declval<T&>(), std::declval<T&>())))
		{
			if(this == &other)
			{
				return;
			}
			base_type::swap(other);
		}
	};

	template< class T >
	inline void swap(optional<T>& lhs, optional<T>& rhs) noexcept(noexcept(lhs.swap(rhs)))
	{
		lhs.swap(rhs);
	}

	template<int..., class T>
	optional<std::decay_t<T> > make_optional(T&& value)
	{
		return std::forward<T>(value);
	}

	template <class T, class ...Args>
	optional<T> make_optional(Args&& ...args)
	{
		return optional<T>(in_place, std::forward<Args>(args)...);
	}

	template <class T, class U, class ...Args>
	optional<T> make_optional(std::initializer_list<U> il, Args&& ...args)
	{
		return optional<T>(in_place, il, std::forward<Args>(args)...);
	}

	template< class T >
	constexpr bool operator==(const optional<T>& lhs, const optional<T>& rhs)
	{
		return bool(lhs) != bool(rhs) ? false : !lhs ? true : *lhs == *rhs;
	}

	template< class T >
	constexpr bool operator!=(const optional<T>& lhs, const optional<T>& rhs)
	{
		return bool(lhs) != bool(rhs) ? true : !lhs ? false : *lhs != *rhs;
	}

	template< class T >
	constexpr bool operator<(const optional<T>& lhs, const optional<T>& rhs)
	{
		return !rhs ? false : (!lhs) ? true : *lhs < *rhs;
	}

	template< class T >
	constexpr bool operator<=(const optional<T>& lhs, const optional<T>& rhs)
	{
		return !lhs ? true : (!lhs) ? false : *lhs <= *rhs;
	}

	template< class T >
	constexpr bool operator>(const optional<T>& lhs, const optional<T>& rhs)
	{
		return !lhs ? false : !rhs ? true : *lhs > * rhs;
	}

	template< class T >
	constexpr bool operator>=(const optional<T>& lhs, const optional<T>& rhs)
	{
		return !rhs ? true : !lhs ? false : *lhs >= *rhs;
	}
	//////////////////////////////////////////////////////////////////////////
	template< class T >
	constexpr bool operator==(const optional<T>& opt, nullopt_t){ return !opt; }

	template< class T >
	constexpr bool operator==(nullopt_t, const optional<T>& opt){ return !opt; }

	template< class T >
	constexpr bool operator!=(const optional<T>& opt, nullopt_t){ return bool(opt); }

	template< class T >
	constexpr bool operator!=(nullopt_t, const optional<T>& opt){ return bool(opt); }


	template< class T >
	constexpr bool operator<(const optional<T>& opt, nullopt_t){ return false; }

	template< class T >
	constexpr bool operator<(nullopt_t, const optional<T>& opt){ return bool(opt); }

	template< class T >
	constexpr bool operator<=(const optional<T>& opt, nullopt_t){ return !opt; }

	template< class T >
	constexpr bool operator<=(nullopt_t, const optional<T>& opt){ return true; }

	template< class T >
	constexpr bool operator>(const optional<T>& opt, nullopt_t){ return bool(opt); }

	template< class T >
	constexpr bool operator>(nullopt_t, const optional<T>& opt){ return false; }

	template< class T >
	constexpr bool operator>=(const optional<T>& opt, nullopt_t){ return true; }
	template< class T >
	constexpr bool operator>=(nullopt_t, const optional<T>& opt){ return !opt; }


	//////////////////////////////////////////////////////////////////////////
	template< class T >
	constexpr bool operator==(const optional<T>& opt, const T& value)
	{
		return bool(opt) ? *opt == value : false;
	}
	template< class T >
	constexpr bool operator==(const T& value, const optional<T>& opt)
	{
		return bool(opt) ? value == *opt : false;
	}

	template< class T >
	constexpr bool operator!=(const optional<T>& opt, const T& value)
	{
		return bool(opt) ? *opt != value : true;
	}

	template< class T >
	constexpr bool operator!=(const T& value, const optional<T>& opt)
	{
		return bool(opt) ? value != *opt : true;
	}

	template< class T >
	constexpr bool operator<(const optional<T>& opt, const T& value)
	{
		return bool(opt) ? *opt < value : true;
	}

	template< class T >
	constexpr bool operator<(const T& value, const optional<T>& opt)
	{
		return bool(opt) ? value < *opt : false;
	}
	template< class T >
	constexpr bool operator<=(const optional<T>& opt, const T& value)
	{
		return bool(opt) ? *opt <= value : true;
	}
	template< class T >
	constexpr bool operator<=(const T& value, const optional<T>& opt)
	{
		return bool(opt) ? value <= *opt : false;
	}

	template< class T >
	constexpr bool operator>(const optional<T>& opt, const T& value)
	{
		return bool(opt) ? *opt > value  : false;
	}
	template< class T >
	constexpr bool operator>(const T& value, const optional<T>& opt)
	{
		return bool(opt) ? value > * opt : true;
	}
	template< class T >
	constexpr bool operator>=(const optional<T>& opt, const T& value)
	{
		return bool(opt) ? *opt >= value : false;
	}
	template< class T >
	constexpr bool operator>=(const T& value, const optional<T>& opt)
	{
		return bool(opt) ? value >= *opt : true;
	}


	template <class T>
	struct nullable_traits<optional<T>> {
		static constexpr nullopt_t none() { return nullopt; }
	};

}


#include "config/config_end.h"
